package com.android.rockchip.camera2;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.RelativeLayout;

import com.android.rockchip.camera2.encoder.MediaAudioEncoder;
import com.android.rockchip.camera2.encoder.MediaEncoder;
import com.android.rockchip.camera2.encoder.MediaMuxerWrapper;
import com.android.rockchip.camera2.encoder.MediaVideoEncoder;
import com.android.rockchip.camera2.shell.Shell;
import com.android.rockchip.camera2.shell.ShellNotFoundException;
import com.android.rockchip.camera2.toast.ToastUtils;
import com.android.rockchip.camera2.util.JniCameraCall;
import com.android.rockchip.camera2.view.CameraGLView;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class RockchipCamera2 extends Activity {
    private static final String TAG = "RockchipCamera2";
    private CameraGLView textureView;
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }
    protected CameraDevice cameraDevice;
    protected CameraCaptureSession cameraCaptureSessions;
    protected CaptureRequest.Builder captureRequestBuilder;
    private Size imageDimension;
    private static final int REQUEST_CAMERA_PERMISSION = 200;
    private Handler mBackgroundHandler;
    private HandlerThread mBackgroundThread;
    private HdmiService mHdmiService;
    private Button video_recode_btn;
    private boolean mPaused = false;
    private AudioStream mAudioStream;
    private boolean mIsRecordingVideo = false; //判断当前是否在录像
    MediaMuxerWrapper mMuxer = null;
    private RelativeLayout rl_bg;

    private String[] permissions = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.RECORD_AUDIO,};
    private boolean isRunningRecording = false;
    private boolean isFirstOpen = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_rockchip_camera2);
        ToastUtils.init(getApplication()); //初始化
        PermissionsUtils.getInstance().chekPermissions(this, permissions, permissionsResult);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mAudioStream = AudioStream.getInstance(getApplicationContext());
        textureView = findViewById(R.id.previewtexture);
        rl_bg = findViewById(R.id.rl_bg);
        textureView.setCameraSurfaceListener(() -> {
            Intent hdmiService = new Intent(RockchipCamera2.this, HdmiService.class);
            hdmiService.setPackage(getPackageName());
            bindService(hdmiService, conn, Context.BIND_AUTO_CREATE);
        });
        video_recode_btn = (Button)findViewById(R.id.video_recode_btn);
        video_recode_btn.setOnClickListener(view -> {
            if(isRunningRecording){
                ToastUtils.show(R.string.fail_start_mult);
                return;
            }
            isRunningRecording = true;
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    isRunningRecording = false;
                }
            }, 1000);
            Log.d(TAG,"press record button，isRecordingVideo="+mIsRecordingVideo);
            if(mIsRecordingVideo){ //当前正在录像
                try{
                    mIsRecordingVideo = false;
                    stopRecordVideo();
                    video_recode_btn.setText(getResources().getString(R.string.start_record));
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            Shell.Console.Builder builder = new Shell.Console.Builder();
                            try {
                                Shell.Console console = builder.useSU().build();
                                console.run("mv "+recordPath+" /sdcard/Movies/");
                                console.close();
                            } catch (ShellNotFoundException e) {
                                e.printStackTrace();
                            }
                            ToastUtils.show(getResources().getString(R.string.file_save)+"/Movies/"+new File(recordPath).getName());
                            File tmpFile = new File("/sdcard/Movies/"+new File(recordPath).getName());
                            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+tmpFile.getAbsolutePath())));
                        }
                    }, 1000);
                }catch (Exception e){
                    e.printStackTrace();
                    noVideo();
                }
            }else{
                Log.d(TAG,"press record button，isAudioStart=" + mAudioStream.isAudioStart);
                if(mAudioStream != null && textureView != null && cameraDevice != null){
                    mIsRecordingVideo = true;
                    recordVideo();
                    video_recode_btn.setText(getResources().getString(R.string.stop_record));
                    ToastUtils.show(getResources().getString(R.string.start_record));
                }else{
                    mIsRecordingVideo = false;
                    video_recode_btn.setText(getResources().getString(R.string.start_record));
                    ToastUtils.show(getResources().getString(R.string.no_video));
                }
            }
        });
    }

    private void noVideo(){
        mIsRecordingVideo = false;
        closeCamera();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ToastUtils.show(getResources().getString(R.string.no_video));
                video_recode_btn.setText(getResources().getString(R.string.start_record));
                mIsRecordingVideo = false;
            }
        });
    }

    private PermissionsUtils.IPermissionsResult permissionsResult = new PermissionsUtils.IPermissionsResult() {
        @Override
        public void passPermissons() {

        }

        @Override
        public void forbitPermissons() {

        }
    };

    private void stopRecordVideo(){
        if(mMuxer != null)
            mMuxer.stopRecording();
    }

    String recordPath = "";
    int preparedCount = 0;
    private void recordVideo(){
        preparedCount = 0;
        try {
            recordPath = getVideoFilePath();
            mMuxer = new MediaMuxerWrapper(recordPath);	// if you record audio only, ".m4a" is also OK.
            MediaAudioEncoder mediaAudioEncoder = new MediaAudioEncoder(mMuxer, mMediaEncoderListener);
            mediaAudioEncoder.init(mAudioStream);
            new MediaVideoEncoder(mMuxer, mMediaEncoderListener, imageDimension.getHeight(), imageDimension.getWidth());
            mMuxer.prepare();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() {
        @Override
        public void onPrepared(final MediaEncoder encoder) {
            preparedCount++;
            if (encoder instanceof MediaVideoEncoder) {
                textureView.setVideoEncoder((MediaVideoEncoder) encoder);
            } else if(encoder instanceof MediaAudioEncoder) {
                MediaAudioEncoder mediaAudioEncoder = (MediaAudioEncoder)encoder;
            }
            if(preparedCount >= 2){
                mMuxer.startRecording();
            }
        }

        @Override
        public void onStopped(final MediaEncoder encoder) {
            if (encoder instanceof MediaVideoEncoder){
                textureView.setVideoEncoder(null);
            }else if(encoder instanceof MediaAudioEncoder){
            }
        }
    };

    private String getVideoFilePath(){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String fileName = simpleDateFormat.format(new Date());
//        return "/storage/emulated/0/Movies/"+fileName+".mp4";
        return "/data/"+fileName+".mp4";
    }

    private void EnableHDMIInAudio(boolean enable) {
        Log.i(TAG, "EnableHDMIInAudio " + enable);
        if (!enable) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mAudioStream.stop();
                }
            }).start();
        } else {
            if (JniCameraCall.getAudioRate() > 0) {
                mAudioStream.start(2);
            }
        }
    }

    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected");
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected");
            // 返回一个HdmiService对象
            mHdmiService = ((HdmiService.HdmiBinder) service).getService();

            // 注册回调接口来接收HDMI的变化
            mHdmiService.setOnHdmiStatusListener(new HdmiService.OnHdmiStatusListener() {

                @Override
                public void onHdmiStatusChange(boolean isHdmiIn, Size driverDimension) {
                    if (mPaused) return;
                    Log.i(TAG, "onHdmiStatusChange isHdmiIn = " + isHdmiIn + ",mPaused:" + mPaused);
                    imageDimension = driverDimension;
                    if (isHdmiIn) {
                        openCamera();
                    } else {
                        closeCamera();
                    }
                }
            });
        }
    };

    private final CameraDevice.StateCallback otherStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            // This is called when the camera is open
            Log.d(TAG, "OtherStateCallback onOpened");
            openCamera();
            camera.close();
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            Log.d(TAG, "OtherStateCallback onDisconnected");
            camera.close();
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            Log.i(TAG, "OtherStateCallback onError="+error);
            camera.close();
            openCamera();
        }
    };


    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            // This is called when the camera is open
            Log.d(TAG, "onOpened");
            cameraDevice = camera;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            Log.d(TAG, "onDisconnected");
            closeCamera();
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            Log.i(TAG, "onError="+error);
            closeCamera();
            openCamera();
        }
    };

    protected void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("Camera Background");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    protected void stopBackgroundThread() {
        if(mBackgroundThread == null){
            return;
        }
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    protected void createCameraPreview() {
        try {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    rl_bg.setVisibility(View.GONE);
                }
            });
            Log.d(TAG, "createCameraPreview");
            SurfaceTexture texture = textureView.getSurfaceTexture();
            assert texture != null;
            Log.d(TAG, "imageDimension.getWidth()=" + imageDimension.getWidth() + ",imageDimension.getHeight()="
                    + imageDimension.getHeight());
            texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());
            Surface surface = new Surface(texture);
            captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            captureRequestBuilder.addTarget(surface);
            cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    // The camera is already closed
                    if (null == cameraDevice) {
                        return;
                    }
                    Log.d(TAG, "onConfigured");
                    // When the session is ready, we start displaying the preview.
                    cameraCaptureSessions = cameraCaptureSession;
                    updatePreview();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    Log.i(TAG, "onConfigureFailed");
                    ToastUtils.show("Configuration failed");
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("MissingPermission")
    private void openCamera() {
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        Log.i(TAG, "openCamera start");
        String[] cameraIdList;
        String cameraId;
        try {
            cameraIdList = manager.getCameraIdList();
            if (cameraIdList.length > 1) {
                cameraId = manager.getCameraIdList()[1];//打开摄像头1
                Log.d(TAG, "Audio Rate = "+JniCameraCall.getAudioRate());
                if(isFirstOpen){
                    String tmpId = manager.getCameraIdList()[0];
                    Log.d(TAG,"open camera id="+tmpId);
                    manager.openCamera(tmpId, otherStateCallback, mBackgroundHandler);
                    isFirstOpen = false;
                }else{
                    Log.d(TAG,"current hdmi input size:"+imageDimension.toString());
                    manager.openCamera(cameraId, stateCallback, mBackgroundHandler);
                }
            } else {
                cameraId = manager.getCameraIdList()[0];//打开摄像头0
                Log.d(TAG,"current hdmi input size:"+imageDimension.toString());
                manager.openCamera(cameraId, stateCallback, mBackgroundHandler);
            }
        } catch (Exception e) {
            e.printStackTrace();
            closeCamera();
        }
        Log.i(TAG, "openCamera end");
    }

    protected void updatePreview() {
        if (null == cameraDevice) {
            Log.e(TAG, "updatePreview error, return");
        }
        Log.d(TAG, "updatePreview");  //表示开启相机成功了
        try {
            captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
            cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    if(!mAudioStream.mIsStartup){
                        EnableHDMIInAudio(true);
                    }
                }
            }, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }catch (IllegalStateException e){
            e.printStackTrace();
        }
    }

    private void closeCamera() {
        Log.d(TAG, "closeCamera");
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                rl_bg.setVisibility(View.VISIBLE);
            }
        });
        if(cameraCaptureSessions != null){
            cameraCaptureSessions.close();
            cameraCaptureSessions = null;
        }
        if (null != cameraDevice) {
            cameraDevice.close();
            cameraDevice = null;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                // close the app
                ToastUtils.show("Sorry!!!, you can't use this app without granting permission");
                finish();
            }
        }
    }

    private Handler handler = new Handler();
    @Override
    protected void onResume() {
        mPaused = false;
        super.onResume();
        Log.d(TAG, "onResume");
        handler.postDelayed(runnable, 300);
    }

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "onResume start");
            startBackgroundThread();
            JniCameraCall.openDevice();
        }
    };

    @Override
    protected void onPause() {
        Log.d(TAG, "onPause");
        mPaused = true;
        closeCamera();
        EnableHDMIInAudio(false);  //加入音频退出
        JniCameraCall.closeDevice();
        handler.removeCallbacks(runnable);
        stopBackgroundThread();
        if(mIsRecordingVideo){
            mIsRecordingVideo = false;
            stopRecordVideo();
            video_recode_btn.setText(getResources().getString(R.string.start_record));
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(1000);
                    Shell.Console.Builder builder = new Shell.Console.Builder();
                    try {
                        Shell.Console console = builder.useSU().build();
                        console.run("mv "+recordPath+" /sdcard/Movies/");
                        console.close();
                    } catch (ShellNotFoundException e) {
                        e.printStackTrace();
                    }
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            File tmpFile = new File("/sdcard/Movies/"+new File(recordPath).getName());
                            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+tmpFile.getAbsolutePath())));
                        }
                    });
                }
            }).start();
        }
        try {
            Log.d(TAG, "unbindService");
            unbindService(conn);
        } catch(Exception e) {
            Log.e(TAG, "exception:" + e);
        }
        super.onPause();
        Shell.Console.Builder builder = new Shell.Console.Builder();
        try {
            Shell.Console console = builder.useSU().build();
            console.run("killall android.hardware.audio.service");
            console.close();
        } catch (ShellNotFoundException e) {
            e.printStackTrace();
        }
        finish();
    }
}
